home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 25 / Cream of the Crop 25.iso / os2 / gnuwget.zip / wget-1.4.3 / src / recur.c < prev    next >
C/C++ Source or Header  |  1997-01-27  |  23KB  |  812 lines

  1. /* Handling of recursive HTTP retrieving.
  2.    Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
  3.    
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2 of the License, or
  7.    (at your option) any later version.
  8.    
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.    
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18.  
  19. #ifdef HAVE_CONFIG_H
  20. #  include <config.h>
  21. #endif /* HAVE_CONFIG_H */
  22.  
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #ifdef HAVE_STRING_H
  26. #  include <string.h>
  27. #else
  28. #  include <strings.h>
  29. #endif /* HAVE_STRING_H */
  30. #ifdef HAVE_UNISTD_H
  31. #  include <unistd.h>
  32. #endif /* HAVE_UNISTD_H */
  33. #include <errno.h>
  34. #include <assert.h>
  35. #include <ctype.h>
  36. #include <sys/types.h>
  37.  
  38. #include "wget.h"
  39. #include "options.h"
  40. #include "url.h"
  41. #include "recur.h"
  42. #include "utils.h"
  43. #include "retr.h"
  44. #include "http.h"
  45. #include "ftp.h"
  46. #include "mtch.h"
  47. #include "host.h"
  48.  
  49. extern struct options opt;
  50.  
  51. extern char *version_string;
  52.  
  53. /* A slist of downloaded URL-s */
  54. urlpos *urls_downloaded;
  55. slist *urls_html;
  56.  
  57. /* The core of recursive retrieving.  Endless recursion is avoided by
  58.    having all URL-s stored to a linked list of URL-s, which is checked
  59.    before loading any URL. That way no URL can get loaded twice.
  60.  
  61.    The function also supports specification of maximum recursion depth
  62.    and a load of goodies. */
  63. uerr_t
  64. recursive_retrieve(const char *file, const char *this_url,
  65.            int flags)
  66. {
  67.    static slist *ulist;         /* List of undesirable-to-load
  68.                    URL-s. */
  69.    static char **forbidden = NULL; /* List of forbidden locations */
  70.    static int depth;            /* Recursion depth */
  71.    static char *base_dir;       /* Base direcory we're recursing from
  72.                    (used by no_parent). */
  73.    static char *robots_host;    /* The host name for which we last
  74.                    checked robots. */
  75.    
  76.    char *constr, *filename, *newloc;
  77.    char *canon_this_url = NULL;
  78.    int dt, inl;
  79.    int this_url_ftp;            /* See below the explanation */
  80.    uerr_t err;
  81.    urlinfo *rurl;
  82.    urlpos *url_list, *cur_url;
  83.    char *rfile; /* For robots */
  84.    urlinfo *u;
  85.  
  86.    if (flags & RCLEANUP)
  87.    {
  88.       free_slist(ulist);
  89.       ulist = NULL;
  90.       free_vec(forbidden);
  91.       forbidden = NULL;
  92.       free_slist(urls_html);
  93.       urls_html = NULL;
  94.       free_urlpos(urls_downloaded);
  95.       urls_downloaded = NULL;
  96.       if (base_dir)
  97.      free(base_dir);
  98.       if (robots_host)
  99.      free(robots_host);
  100.       return RETROK;
  101.    }
  102.    assert(this_url != NULL);
  103.    assert(file != NULL);
  104.    /* If quota was exceeded earlier, bail out. */
  105.    if (opt.quota && (opt.downloaded > opt.quota))
  106.       return QUOTEXC;
  107.    /* Cache the current URL in the list. */
  108.    if (flags & RFIRST_TIME)
  109.    {
  110.       ulist = add_slist(ulist, this_url, 0);
  111.       urls_downloaded = NULL;
  112.       urls_html = NULL;
  113.       /* Enter this_url to the slist, in original and "enhanced"
  114.      form. */
  115.       u = newurl();
  116.       err = parseurl(this_url, u, 0);
  117.       if (err == URLOK)
  118.       {
  119.      ulist = add_slist(ulist, u->url, 0);
  120.      urls_downloaded = add_url(urls_downloaded, u->url, file);
  121.      urls_html = add_slist(urls_html, file, NOSORT);
  122.      if (opt.no_parent)
  123.         base_dir = nstrdup(u->dir); /* Set the base dir. */
  124.      /* Set the canonical this_url to be sent as referer.  This
  125.         problem exists only when running the first time. */
  126.      canon_this_url = nstrdup(u->url);
  127.       }
  128.       else
  129.       {
  130.      DEBUGP("Double yuck!  The *base* URL is broken.\n");
  131.      base_dir = NULL;
  132.       }
  133.       freeurl(u, 1);
  134.       depth = 1;
  135.       robots_host = NULL;
  136.       forbidden = NULL;
  137.    }
  138.    else
  139.       ++depth;
  140.    
  141.    /* Bail out if opt.maxreclevel is exceeded. */
  142.    if ((opt.maxreclevel != 0) && (depth > opt.maxreclevel))
  143.    {
  144. #ifdef DEBUG
  145.       if (opt.debug)
  146.      fprintf(opt.lfile, "Recursion depth %d exceeded max. depth %d.\n",
  147.          depth, opt.maxreclevel);
  148. #endif
  149.       --depth;
  150.       return RECLEVELEXC;
  151.    }
  152.  
  153.    /* Determine whether this_url is an FTP URL. If it is, it means
  154.       that the retrieval is done through proxy. In that case, FTP
  155.       links will be followed by default and recursion will not be
  156.       turned off when following them. */
  157.    this_url_ftp = (urlproto(this_url) == URLFTP);
  158.  
  159.    /* Get the URL-s from an HTML file: */
  160.    url_list = get_urls_html(file,
  161.                 canon_this_url ? canon_this_url : this_url, 0);
  162.  
  163.    /* Decide what to do with each of the URLs. A URL will be loaded if
  164.       it meets several requirements, discussed later. */
  165.    for (cur_url = url_list; cur_url; cur_url = cur_url->next)
  166.    {
  167.       /* If quota was exceeded earlier, bail out. */
  168.       if (opt.quota && (opt.downloaded > opt.quota))
  169.      break;
  170.       /* Parse the URL for convenient use in other functions, as well
  171.      as to get the optimized form. It also checks URL
  172.      integrity. */
  173.       u = newurl();
  174.       if (parseurl(cur_url->url, u, 0) != URLOK)
  175.       {
  176.      DEBUGP("Yuck! A bad URL.\n");
  177.      freeurl(u, 1);
  178.      continue;
  179.       }
  180.       if (u->proto == URLFILE)
  181.       {
  182.      DEBUGP("Nothing to do with file:// around here.\n");
  183.      freeurl(u, 1);
  184.      continue;
  185.       }
  186.       assert(u->url != NULL);
  187.       constr = nstrdup(u->url);
  188.       
  189.       /* Several checkings whether a file is acceptable to load:
  190.      1. check if URL is ftp, and we don't load it
  191.      2. check for relative links (if relative_only is set)
  192.      3. check for domain
  193.      4. check for no-parent
  194.      5. check for excludes && includes
  195.      6. check for suffix
  196.      7. check for same host (if spanhost is unset), with possible
  197.      gethostbyname baggage
  198.      8. check for robots.txt
  199.  
  200.      Addendum: If the URL is FTP, and it is to be loaded, only the
  201.      domain and suffix settings are "stronger".
  202.      
  203.      Note that .html and (yuck) .htm will get loaded
  204.      regardless of suffix rules (but that is remedied later with
  205.      unlink).
  206.  
  207.      More time- and memory- consuming tests should be put later on
  208.      the list.  */
  209.  
  210.       /* inl is set if the URL we are working on (constr) is stored in
  211.      ulist. Its usage is crucial to avoid the constant calls to
  212.      in_slist, which is quite slow. */
  213.       inl = in_slist(ulist, constr);
  214.       
  215.       /* If it is FTP, and FTP is not followed, chuck it out. */
  216.       if (!inl)
  217.      if (u->proto == URLFTP && !opt.follow_ftp && !this_url_ftp)
  218.      {
  219.         DEBUGP("Uh, it is FTP but i'm not in the mood to follow FTP.\n");
  220.         ulist = add_slist(ulist, constr, 0);
  221.         inl = 1;
  222.      }
  223.       /* If it is absolute link and they are not followed, chuck it
  224.      out. */
  225.       if (!inl && u->proto != URLFTP)
  226.      if (opt.relative_only && !(cur_url->flags & URELATIVE))
  227.      {
  228.         DEBUGP("It doesn't really look like a relative link.\n");
  229.         ulist = add_slist(ulist, constr, 0);
  230.         inl = 1;
  231.      }
  232.       /* If its domain is not to be accepted/looked-up, chuck it
  233.      out. */
  234.       if (!inl)
  235.      if (!accept_domain(u))
  236.      {
  237.         DEBUGP("I don't like the smell of that domain.\n");
  238.         ulist = add_slist(ulist, constr, 0);
  239.         inl = 1;
  240.      }
  241.       /* Check for parent directory. */
  242.       if (!inl && opt.no_parent)
  243.       {
  244.      /* Check for base_dir first. */
  245.      if (!(base_dir && frontcmp(base_dir, u->dir)))
  246.      {
  247.         /* Failing that, check for parent dir. */
  248.         urlinfo *ut = newurl();
  249.         if (parseurl(this_url, ut, 0) != URLOK)
  250.            DEBUGP("Double yuck!  The *base* URL is broken.\n");
  251.         else if (!frontcmp(ut->dir, u->dir))
  252.         {
  253.            /* Failing that too, kill the URL. */
  254.            DEBUGP("Trying to escape parental guidance with no_parent on.\n");
  255.            ulist = add_slist(ulist, constr, 0);
  256.            inl = 1;
  257.         }
  258.         freeurl(ut, 1);
  259.      }
  260.       }
  261.       /* If the file does not match the acceptance list, or is on the
  262.      rejection list, chuck it out.  The same goes for the
  263.      directory exclude- and include- lists.  */
  264.       if (!inl && (opt.includes || opt.excludes))
  265.       {
  266.      if (!accdir(u->dir, ALLABS))
  267.      {
  268. #ifdef DEBUG
  269.         if (opt.debug)
  270.            fprintf(opt.lfile, "%s (%s) is excluded/not-included.\n",
  271.                constr, u->dir);
  272. #endif
  273.         ulist = add_slist(ulist, constr, 0);
  274.         inl = 1;
  275.      }
  276.       }
  277.       if (!inl)
  278.       {
  279.      char *suf = NULL;
  280.      /* We check for acceptance/rejection rules only for non-HTML
  281.         documents.  Since we don't know whether they really are
  282.         HTML, it will be deduced from (an OR-ed list):
  283.         
  284.         1) u->file is "" (meaning it is a directory)
  285.         2) suffix exists, AND:
  286.         a) it is "html", OR
  287.         b) it is "htm"
  288.         
  289.         If the file *is* supposed to be HTML, it will *not* be
  290.         subject to acc/rej rules.  That's why the `!'. */
  291.      if (!
  292.          (!*u->file
  293.           || (((suf = suffix(constr)) != NULL)
  294.           && (!strcmp(suf, "html") || !strcmp(suf, "htm")))))
  295.      {
  296.         if (!acceptable(u->file))
  297.         {
  298. #ifdef DEBUG
  299.            if (opt.debug)
  300.           fprintf(opt.lfile,
  301.               "%s (%s) does not match acc/rej rules.\n",
  302.               constr, u->file);
  303. #endif
  304.            ulist = add_slist(ulist, constr, 0);
  305.            inl = 1;
  306.         }
  307.      }
  308.      if (suf)
  309.         free(suf);
  310.       }
  311.       /* Optimize the URL (which includes possible DNS lookup) only
  312.      after all other possibilities have been exhausted. */
  313.       if (!inl)
  314.       {
  315.      if (!opt.simple_check)
  316.         opt_url(u);
  317.      free(constr);
  318.      constr = nstrdup(u->url);
  319.      inl = in_slist(ulist, constr);
  320.      if (!inl && !((u->proto == URLFTP) && !this_url_ftp))
  321.         if (!opt.spanhost && this_url && !same_host(this_url, constr))
  322.         {
  323.            DEBUGP("This is not the same hostname as the parent's.\n");
  324.            ulist = add_slist(ulist, constr, 0);
  325.            inl = 1;
  326.         }
  327.       }
  328.       /* What about robots.txt? */
  329.       if (!inl && opt.use_robots && u->proto == URLHTTP)
  330.       {
  331.      /* Since Wget knows about only one set of robot rules at a
  332.         time, /robots.txt must be reloaded whenever a new host is
  333.         accessed.
  334.  
  335.         robots_host holds the host the current `forbid' variable
  336.         is assigned to.  */
  337.      if (!robots_host || !same_host(robots_host, u->host))
  338.      {
  339.         if (robots_host)
  340.            free(robots_host);
  341.         /* Now make robots_host the new host, no matter what the
  342.            result will be.  So if there is no /robots.txt on the
  343.            site, Wget will not retry getting robots all the
  344.            time. */
  345.         robots_host = nstrdup(u->host);
  346.         free_vec(forbidden);
  347.         forbidden = NULL;
  348.         err = retrieve_robots(constr, ROBOTS_FILENAME);
  349.         if (err == ROBOTSOK)
  350.         {
  351.            rurl = robots_url(constr, ROBOTS_FILENAME);
  352.            rfile = url_filename(rurl);
  353.            forbidden = parse_robots(rfile);
  354.            freeurl(rurl, 1);
  355.            free(rfile);
  356.         }
  357.      }
  358.  
  359.      /* Now that we have (or don't have) robots, we can check for
  360.         them. */
  361.      if (!robots_match(u, forbidden))
  362.      {
  363. #ifdef DEBUG
  364.         if (opt.debug)
  365.            fprintf(opt.lfile, "Stuffing %s because %s forbids it.\n",
  366.                this_url, ROBOTS_FILENAME);
  367. #endif
  368.         ulist = add_slist(ulist, constr, 0);
  369.         inl = 1;
  370.      }
  371.       }
  372.  
  373.       filename = NULL;
  374.       /* If it wasn't chucked out, do something with it. */
  375.       if (!inl)
  376.       {
  377.      DEBUGP("I've decided to load it -> ");
  378.      /* Add it to the list of already-loaded URL-s. */
  379.      ulist = add_slist(ulist, constr, 0);
  380.      /* Automatically followed FTPs will *not* be downloaded
  381.         recursively. */
  382.      if (u->proto == URLFTP)
  383.      {
  384.         /* Don't you adore side-effects? */
  385.         opt.recursive = 0;
  386.      }
  387.      /* Reset its type. */
  388.      dt = 0;
  389.      /* Retrieve it. */
  390.      retrieve_url(constr, &filename, &newloc,
  391.               canon_this_url ? canon_this_url : this_url, &dt);
  392.      if (u->proto == URLFTP)
  393.      {
  394.         /* Restore... */
  395.         opt.recursive = 1;
  396.      }
  397.      if (newloc)
  398.      {
  399.         free(constr);
  400.         constr = newloc;
  401.      }
  402.      /* In case of convert_links: If there was no error, add it to
  403.         the list of downloaded URLs.  We might need it for
  404.         conversion.  */
  405.      if (opt.convert_links && filename)
  406.      {
  407.         if (dt & RETROKF)
  408.         {
  409.            urls_downloaded = add_url(urls_downloaded, constr, filename);
  410.            /* If the URL is HTML, note it. */
  411.            if (dt & TEXTHTML)
  412.           urls_html = add_slist(urls_html, filename, NOSORT);
  413.         }
  414.      }
  415.      /* If it is not an error, and it is of type text/html, parse
  416.         it recursively. */
  417.      if ((dt & TEXTHTML) && (dt & RETROKF))
  418.         recursive_retrieve(filename, constr, 0);
  419. #ifdef DEBUG
  420.      else if (opt.debug)
  421.         fprintf(opt.lfile, "%s is not text/html so we don't chase.\n",
  422.             filename ? filename: "(null)");
  423. #endif
  424.      /* If an suffix-rejected file was loaded only because it was HTML,
  425.         undo the error now */
  426.      if (opt.delete_after || (filename && !acceptable(filename)))
  427.      {
  428.         if (opt.verbose)
  429.            fprintf(opt.lfile, "Removing %s%s\n", filename,
  430.                opt.delete_after ? "." :
  431.                " since it should be rejected.");
  432.         if (unlink(filename) && !opt.quiet)
  433.            fprintf(opt.lfile, "unlink: %s\n", mystrerror(errno));
  434.         dt &= ~RETROKF;
  435.      }
  436.      /* If everything was OK, and links are to be converted, let's
  437.         store the local filename. */
  438.      if (opt.convert_links && (dt & RETROKF) && (filename != NULL))
  439.      {
  440.         cur_url->flags |= UABS2REL;
  441.         cur_url->local_name = nstrdup(filename);
  442.      }
  443.       }
  444. #ifdef DEBUG
  445.       else if (opt.debug)
  446.      fprintf(opt.lfile, "%s already in list, so we don't load.\n", constr);
  447. #endif
  448.       /* Free filename and constr. */
  449.       if (filename)
  450.      free(filename);
  451.       if (constr)
  452.      free(constr);
  453.       freeurl(u, 1);
  454.       /* Increment the pbuf for the appropriate size. */
  455.    }
  456.    if (opt.convert_links)
  457.       convert_links(file, url_list);
  458.    /* Free the linked list of URL-s. */
  459.    free_urlpos(url_list);
  460.    /* Free the canonical this_url. */
  461.    if (canon_this_url)
  462.       free(canon_this_url);
  463.    /* Decrement the recursion depth. */
  464.    --depth;
  465.    if (opt.quota && (opt.downloaded > opt.quota))
  466.       return QUOTEXC;
  467.    else
  468.       return RETROK;
  469. }
  470.  
  471. /* Simple calls to convert_links will often fail because only the
  472.    downloaded files are converted, and Wget cannot know which files
  473.    will be converted in the future.  So, if we have file fileone.html
  474.    with:
  475.    
  476.    <a href=/c/something.gif>
  477.    
  478.    and /c/something.gif was not downloaded because it exceeded the
  479.    recursion depth, the reference will *not* be changed.
  480.    
  481.    However, later we can encounter /c/something.gif from an "upper"
  482.    level HTML (let's call it filetwo.html), and it gets downloaded.
  483.    
  484.    But now we have a problem because /c/something.gif will be
  485.    correctly transformed in filetwo.html, but not in fileone.html,
  486.    since Wget could not have known that /c/something.gif will be
  487.    downloaded in the future.
  488.  
  489.    This is why Wget must, after the whole retrieval, call
  490.    convert_all_links to go once more through the entire list of
  491.    retrieved HTML-s, and re-convert them.
  492.  
  493.    All the downloaded HTMLs are kept in urls_html, and downloaded URLs
  494.    in urls_downloaded.  From these two lists information is
  495.    extracted. */
  496. void
  497. convert_all_links(void)
  498. {
  499.    uerr_t res;
  500.    urlpos *l1, *l2, *urls;
  501.    urlinfo *u;
  502.    slist *html;
  503.    urlpos *urlhtml;
  504.  
  505.    for (html = urls_html; html; html = html->next)
  506.    {
  507. #ifdef DEBUG
  508.       if (opt.debug)
  509.      fprintf(opt.lfile, "Rescanning %s\n", html->string);
  510. #endif
  511.       /* Determine the URL of the HTML file.  get_urls_html will need
  512.      it. */
  513.       for (urlhtml = urls_downloaded; urlhtml; urlhtml = urlhtml->next)
  514.      if (!strcmp(urlhtml->local_name, html->string))
  515.         break;
  516. #ifdef DEBUG
  517.       if (opt.debug)
  518.       {
  519.      if (urlhtml)
  520.         fprintf(opt.lfile, "It should correspond to %s.\n", urlhtml->url);
  521.      else
  522.         fprintf(opt.lfile, "I cannot find the corresponding URL.\n");
  523.       }
  524. #endif
  525.       /* Parse the HTML file... */
  526.       urls = get_urls_html(html->string, urlhtml ? urlhtml->url : NULL, 1);
  527.       if (!urls)
  528.      continue;
  529.       for (l1 = urls; l1; l1 = l1->next)
  530.       {
  531.      /* The URL must be in canonical form to be compared. */
  532.      u = newurl();
  533.      res = parseurl(l1->url, u, 0);
  534.      if (res != URLOK)
  535.      {
  536.         freeurl(u, 1);
  537.         continue;
  538.      }
  539.      /* We decide the direction of conversion according to whether
  540.         a URL was downloaded.  Downloaded URLs will be converted
  541.         ABS2REL, whereas non-downloaded will be converted REL2ABS.
  542.         Note: not yet implemented; only ABS2REL works.  */
  543.      for (l2 = urls_downloaded; l2; l2 = l2->next)
  544.         if (!strcmp(l2->url, u->url))
  545.         {
  546. #ifdef DEBUG
  547.            if (opt.debug)
  548.           fprintf(opt.lfile,
  549.               "%s flagged for conversion, local %s\n",
  550.               l2->url, l2->local_name);
  551. #endif
  552.            break;
  553.         }
  554.      /* Clear the flags. */
  555.      l1->flags &= ~(UABS2REL | UREL2ABS);
  556.      /* Decide on the conversion direction. */
  557.      if (l2)
  558.      {
  559.         l1->flags |= UABS2REL;
  560.         l1->local_name = nstrdup(l2->local_name);
  561.      }
  562.      else
  563.      {
  564.         l1->flags |= UREL2ABS;
  565.         l1->local_name = NULL;
  566.      }
  567.      freeurl(u, 1);
  568.       }
  569.       /* Convert the links in the file. */
  570.       convert_links(html->string, urls);
  571.       /* Free the data. */
  572.       free_urlpos(urls);
  573.    }
  574. }
  575.  
  576.  
  577. /*******************
  578.  * Robots support:
  579.  *******************/
  580.  
  581. /* Construct the robots URL. */
  582. urlinfo *
  583. robots_url(const char *url, const char *robots_filename)
  584. {
  585.    urlinfo *u = newurl();
  586.    uerr_t err;
  587.  
  588.    err = parseurl(url, u, 0);
  589.    assert(err == URLOK && u->proto == URLHTTP);
  590.    free(u->file);
  591.    free(u->dir);
  592.    free(u->url);
  593.    u->dir = nstrdup("");
  594.    u->file = nstrdup(robots_filename);
  595.    u->url = str_url(u, 0);
  596.    return u;
  597. }
  598.  
  599. /* Retrieves the robots_filename from the root server directory, if
  600.    possible. Returns ROBOTSOK if robots were retrieved OK, and
  601.    NOROBOTS if robots could not be retrieved for any reason. */
  602. uerr_t
  603. retrieve_robots(const char *url, const char *robots_filename)
  604. {
  605.    int dt;
  606.    uerr_t err;
  607.    urlinfo *u;
  608.  
  609.    u = robots_url(url, robots_filename);
  610.    if (opt.verbose)
  611.       fprintf(opt.lfile, "Loading robots.txt; please ignore errors.\n");
  612.    err = retrieve_url(u->url, NULL, NULL, NULL, &dt);
  613.    freeurl(u, 1);
  614.    if (err == RETROK)
  615.       return ROBOTSOK;
  616.    else
  617.       return NOROBOTS;
  618. }
  619.  
  620. /* Parses the robots_filename returning the disallowed filenames in a
  621.    malloc-ed vector of character pointers.
  622.  
  623.    It should be fully compliant with the syntax as described in the
  624.    file norobots.txt, adopted by the robots mailing list
  625.    (robots@webcrawler.com). */
  626. char **
  627. parse_robots(const char *robots_filename)
  628. {
  629.    FILE *fp;
  630.    char **entries;
  631.    char *line, *cmd, *str, *p;
  632.    char *base_version, *version;
  633.    int len, num, i;
  634.    int wgetmatched;           /* Is the part meant for Wget? */
  635.  
  636.    entries = NULL;
  637.  
  638.    num = 0;
  639.    fp = fopen(robots_filename, "r");
  640.    if (!fp)
  641.       return NULL;
  642.    
  643.    /* Kill version number. */
  644.    base_version = nstrdup(version_string);
  645.    version = nstrdup(version_string);
  646.    for (p = version; *p; p++)
  647.       *p = tolower(*p);
  648.    for (p = base_version; *p && *p != '/'; p++)
  649.       *p = tolower(*p);
  650.    *p = '\0';
  651.  
  652.    /* Setting this to 1 means that Wget considers itself under
  653.       restrictions by default, even if the User-Agent field is not
  654.       present. However, if it finds the user-agent set to something
  655.       other than Wget, the rest will be ignored (up to the following
  656.       User-Agent field). Thus you may have something like:
  657.       
  658.       Disallow: 1
  659.       Disallow: 2
  660.       User-Agent: stupid-robot
  661.       Disallow: 3
  662.       Disallow: 4
  663.       User-Agent: Wget*
  664.       Disallow: 5
  665.       Disallow: 6
  666.       User-Agent: *
  667.       Disallow: 7
  668.  
  669.       In this case the 1, 2, 5, 6 and 7 disallow lines will be
  670.       stored. */
  671.    wgetmatched = 1;
  672.    while ((line = read_whole_line(fp)))
  673.    {
  674.       len = strlen(line);
  675.       /* Destroy <CR> if there is one. */
  676.       if (len && line[len - 1] == '\r')
  677.      line[len - 1] = '\0';
  678.       /* According to specifications, optional space may be at the
  679.      end... */
  680. #ifdef DEBUG
  681.       if (opt.debug)
  682.      fprintf(opt.lfile, "Line: %s\n", line);
  683. #endif
  684.       /* Skip spaces. */
  685.       for (cmd = line; *cmd && isspace(*cmd); cmd++);
  686.       if (!*cmd)
  687.       {
  688.      free(line);
  689.      DEBUGP("(chucked out)\n");
  690.      continue;
  691.       }
  692.       /* Look for ':'. */
  693.       for (str = cmd; *str && *str != ':'; str++);
  694.       if (!*str)
  695.       {
  696.      free(line);
  697.      DEBUGP("(chucked out)\n");
  698.      continue;
  699.       }
  700.       /* Zero-terminate the command. */
  701.       *str++ = '\0';
  702.       /* Look for the string beginning... */
  703.       for (; *str && isspace(*str); str++);
  704.       /* Look for comments and kill them off. */
  705.       for (p = str; *p && !isspace(*p); p++);
  706.       if (isspace(*p) && *(p + 1) == '#')
  707.      *p = '\0';
  708.       if (!strcasecmp(cmd, "User-agent"))
  709.       {
  710.      int match = 0;
  711.      /* Lowercase the agent string. */
  712.      for (p = str; *p; p++)
  713.         *p = tolower(*p);
  714.      /* If the string is '*', it matches. */
  715.      if (*str == '*' && !*(str + 1))
  716.         match = 1;
  717.      else
  718.      {
  719.         /* If the string contains wildcards, we'll run it through
  720.            fnmatch(). */
  721.         if (has_wildcards(str))
  722.         {
  723.            /* If the string contains '/', compare with the full
  724.           version.  Else, compare it to base_version.  */
  725.            if (strchr(str, '/'))
  726.           match = !fnmatch(str, version, 0);
  727.            else
  728.           match = !fnmatch(str, base_version, 0);
  729.         }
  730.         else                /* Substring search */
  731.         {
  732.            if (strstr(version, str))
  733.           match = 1;
  734.            else
  735.           match = 0;
  736.         }
  737.      }
  738.      /* If Wget is not matched, skip all the entries up to the
  739.         next User-agent field. */
  740.      wgetmatched = match;
  741.       }
  742.       else if (!wgetmatched)
  743.       {
  744.      free(line);
  745.      DEBUGP("(chucking out since it is not applicable for Wget)\n");
  746.      continue;
  747.       }
  748.       else if (!strcasecmp(cmd, "Disallow"))
  749.       {
  750.      /* If "Disallow" is empty, the robot is welcome.  */
  751.      if (!*str)
  752.      {
  753.         free_vec(entries);
  754.         entries = (char **)nmalloc(sizeof(char *));
  755.         *entries = NULL;
  756.      }
  757.      else
  758.      {
  759.         entries = (char **)nrealloc(entries, (num + 2)* sizeof(char *));
  760.         entries[num] = nstrdup(str);
  761.         entries[++num] = NULL;
  762.         /* Strip trailing spaces, according to specifications. */
  763.         for (i = strlen(str); i >= 0 && isspace(str[i]); i--)
  764.            if (isspace(str[i]))
  765.           str[i] = '\0';
  766.      }
  767.       }
  768.       else
  769.       {
  770.      /* unknown command */
  771.      DEBUGP("(chucked out)\n");
  772.       }
  773.       free(line);
  774.    }
  775.    fclose(fp);
  776.    free(base_version);
  777.    free(version);
  778.    return entries;
  779. }
  780.  
  781. /* May the URL url be loaded according to disallowing rules stored in
  782.    forbidden? */
  783. int
  784. robots_match(urlinfo *u, char **forbidden)
  785. {
  786.    int l;
  787.  
  788.    if (!forbidden)
  789.       return 1;
  790. #ifdef DEBUG
  791.    if (opt.debug)
  792.       fprintf(opt.lfile, "Matching %s against: ", u->path);
  793. #endif
  794.    for (; *forbidden; forbidden++)
  795.    {
  796. #ifdef DEBUG
  797.       if (opt.debug)
  798.      fprintf(opt.lfile, "%s ", *forbidden);
  799. #endif
  800.       l = strlen(*forbidden);
  801.       /* If dir is forbidden, we may not load the file. */
  802.       if (strncmp(u->path, *forbidden, l) == 0)
  803.       {
  804.      DEBUGP("matched.\n");
  805.      return 0; /* Matches, i.e. does not load... */
  806.       }
  807.    }
  808.    DEBUGP("not matched.\n");
  809.    return 1;
  810. }
  811.  
  812.